/**
 *
 *  Util.Plot.TimeSeries, V0.7
 *
 *  @author Michael Liljenstam
 *
 */


package SSF.Util.Plot;

import SSF.OS.NetFlow.*;
import SSF.Util.Streams.*;
import SSF.OS.NetFlow.Filter.*;
import com.renesys.raceway.DML.*;

import ptolemy.plot.*;
import java.awt.*;
import java.awt.event.*;

/**
 *  Component for plotting time-series of data (NIC queue data and netflow 
 *  data). <br>
 *  
 *  Simple application wrapper to bin data, view time-series or output to 
 *  file. Generate live (animated) plot or plot file (PlotML or ascii). 
 *  <br> <br>
 *
 *  Use -help option for help on usage and options.
 *  <br><br>
 *  
 *  TO DO: <br><br>
 *      <li> Inefficient, has to aggregate the whole trace in memory before
 *           plotting. </li><br><br>
 *
 *       </blockquote>
 *
 *  @author Michael Liljenstam
 *  $Id: TimeSeries.java,v 1.16 2003/02/13 19:34:24 mili Exp $
 */
public class TimeSeries {

    /* 
     *  Constants
     */

    /** Default setting for the time-bin size */
    static public  final float    BIN_SIZE = 1;
    /** Width of bars as fraction of time-bin size */
    static public  final float    BAR_WIDTH = 0.9F;

    // plot types enumeration
    static protected final char     PLOTML_FILE       = 0;
    static protected final char     LIVE_FROM_FILE    = 1;
    static protected final char     STATIC_FROM_FILE  = 2;
    static protected final char     ASCII_FILE        = 3;
    //    static protected final char     LIVE              = 4;


    static protected final int      POINT_PLOT_DELAY = 0;   // delay [ms] between
				                          // each point plotted
							  // (default)
    static protected final int      PLOT_LOOP_DELAY = 10;   // delay [ms] between
				                          // each loop in the 
							  // animated plot

    /** Default setting to rescale live plot after each data point. */
    static public  final int        RESCALE_STEP = 1;


    // Symbolic names corresponding to field identifiers in Analyzer inner 
    // class. For some reason Java doesn't allow def of static String 
    // arrays like this one in an in inner class. Not obvious why this should 
    // be?
    public static final String[] FIELD_SYMBS = 
    {"unspec", "qlen", "pktcnt", "pktdropcnt", "pktdiff", 
     "pktdropdiff"};

    /* 
     *  Attributes
     */

    /** Data input for data generated from file. Each row is one dataset. */
    public  Object[][]      dataVector;

    /** DTD file default (for PlotML XML definition). */
    String   dtdFile = "http://ptolemy.eecs.berkeley.edu/xml/dtd/PlotML_1.dtd";

    /** Delay between each point [ms] */
    static int plotDelay = POINT_PLOT_DELAY;

    /** DML filter config files if filter is used, null = no filter. */
    String[] filterFiles = null;

    /** Number of datasets to plot at once. */
    private  int           numDatasets = 1;

    /** Plot type, default: bar-graph, false = line-graph */
    private  boolean       barGraph = true;

    /** Window size for sliding time-window plot. In time units according 
        to x-axis. 0 = not a time-window plot. >0 = time-window enabled. */
    private  float         windowSize = 0;

    /** Plot window (each graphs) height in pixels. To initialize with a 
	different window size.
     */
    private  int         plotWinHeights = 0;


    ///////////////////////////////////////////////////////////////////
    ////                     constructors & methods                ////

    /**
     *  Default constructor. Does nothing. (handled by "main")
     */

    public TimeSeries() {}


    /**
     *  Write a header for PlotML formatted output for ptplot
     *
     */

    private void writePlotMLHeader(String title, float binSize) {
	System.out.println("<?xml version=\"1.0\" standalone=\"no\"?>");
	System.out.println("<!DOCTYPE plot SYSTEM \"" + dtdFile + "\">");
	System.out.println("<plot>");
	System.out.println("<title>" + title + "</title>");
	if (barGraph) {
	    System.out.println("<barGraph width=\"" + binSize * BAR_WIDTH +
			       "\" offset=\"" + 0 + "\"/>");
	    System.out.println("<dataset connected=\"no\">");
	} else {
	    System.out.println("<dataset>");
	}
    }


    /**
     *  Write flow data vector in PlotML format
     */

    public void writePlotMLdata(float binSize) {

	float xOffset = 0;
	if (barGraph) {
	    xOffset = 0.5F;
	}

	for (int j=0; j<numDatasets; j++) {
	    for (int i=0; i<dataVector[j].length; i++) {
		float value = 0;
		if (dataVector[j][i] != null)
		    value = ((Float) dataVector[j][i]).floatValue();
		System.out.println("<point " + " x=\"" + 
				   (i + xOffset) * binSize +
				   "\" y=\"" + value + "\">");
	    }
	    // force output of one last point back to zero flows
	    if (!barGraph)
		System.out.println("<point " + " x=\"" + 
				   (dataVector[j].length + xOffset) * binSize +
				   "\" y=\"" + 0 + "\">");
	}
    }

    /**
     *  Write a trailer for PlotML formatted output for ptplot
     *
     */

    private void writePlotMLTrailer() {
	System.out.println("</dataset>");
	System.out.println("</plot>");
    }


    /**
     *  Write flow data vector in ascii format
     */

    public void writeAsciidata(float binSize) {

	for (int j=0; j<numDatasets; j++) {
	    int i;
	    for (i=0; i<dataVector[j].length; i++) {
		float value = 0;
		if (dataVector[j][i] != null)
		    value = ((Float) dataVector[j][i]).floatValue();
		System.out.println(i * binSize + "     " + value);
	    }
	    // force output of one last point back to zero flows
	    if (!barGraph)
		System.out.println(i * binSize + "     " + 0);
	}
    }


    static public void printHelp() {
	System.err.println("");
	System.err.println("usage: JAVA TimeSeries [options] <dumpfilename>");
	System.err.println("");
	System.err.println("");
	System.err.println("Default behavior is to output an ASCII file in PlotML format to stdout.");
	System.err.println("Use -live or -static options to display a graph.");

	System.err.println("");
	System.err.println("options:");
	System.err.println("");
	System.err.println(" -binsize <float>              Set time-bin size.");
	System.err.println("");
	System.err.println(" -filter <filename>[,<field>]  Invoke filter given DML filename for filter");
	System.err.println("                               configuration. Optionally also specify a");
	System.err.println("                               field in the record. Field identifiers");
	System.err.println("                               for NIC queue stats are:");
	System.err.println("                               qlen       - queue length");
	System.err.println("                               pktcnt     - packet count (cumulative)");
	System.err.println("                               pktdropcnt - packet drop count (cumulative)");
	System.err.println("                               pktdiff    - differentiated (instant) packet"); 
	System.err.println("                                            count. (deprecated)"); 
	System.err.println("                               pktdropdiff - differentiated (instant) packet");
	System.err.println("                                             drop count. (deprecated)");
	System.err.println("");
	System.err.println(" -datasets <int>               Number of datasets for multiple (time-aligned)");
	System.err.println("                               plots.");
	System.err.println("");
	System.err.println(" -live                         Do a 'live' plot");
	System.err.println("");
	System.err.println(" -static                       Do a standard 'static' plot.");
	System.err.println("                               (No pseudo-live updates.)");
	System.err.println("");
	System.err.println(" -rescalestep <int>            Rescale live plot after every n points");
	System.err.println("");
	System.err.println(" -delay <int>                  Inter-point plot delay time [ms]");
	System.err.println("");
	System.err.println(" -win_heights <int>            Set (initial) height of each (sub-)plot [pixels]");
	System.err.println("");

	System.err.println(" -window <float>               Specify a time-window [time-units] for a");
	System.err.println("                               'sliding window' plot.");
	System.err.println("");
	System.err.println(" -ascii                        Output in simple ascii format (for MATLAB).");
	System.err.println("");
	System.err.println(" -dtd <filename>               Document Type Definition file (for PlotML).");
	System.err.println("");
	System.err.println("or 'JAVA TimeSeries -help', for this information.");
	System.err.println("");
	System.err.println("NOTE: the -filter option can be give several times for multiple datasets (each filter will be matched to one dataset). If the number of filters is > the number of datasets, the datasets parameter will be adjusted automatically.");
    }

    public static void main(String args[])
    {
	// init defaults (method local)
	float binSize = BIN_SIZE;
	char  plotType = PLOTML_FILE;

	// local vars
	int   numFilters = 0;
	int   filterFileIndex = 0;
	char[] fieldSelectors = null; // list of record fields to plot
	int   rescaleStep = RESCALE_STEP;
	
	// create plot obj
	TimeSeries thisPlot = new TimeSeries();

	// check for missing arguments
	if (args.length < 1) {
	    printHelp();
	    return;
	}

	// check number of filters
	for (int i=0; i<args.length; i++) {
	    if (args[i].equals("-filter"))
		numFilters++;
	}
	if (numFilters > 0) {
	    thisPlot.filterFiles = new String[numFilters];
	    fieldSelectors = new char[numFilters];
	}
	    

	// parse options
	int fileNameIndex = 0;
	for (int i=0; i<args.length; i++) {
	    if (args[i].equals("-binsize")) {
		try {
		    binSize = Float.parseFloat(args[++i]);
		} catch (java.lang.NumberFormatException e) {
		    System.err.println("");
		    System.err.println("ERROR: -binsize argument found: " +
				       args[i] + ", should be float.");
		    return;
		}
		fileNameIndex += 2;
	    }
	    if (args[i].equals("-live")) {
		plotType = LIVE_FROM_FILE;
		fileNameIndex++;
	    }
	    if (args[i].equals("-static")) {
		plotType = STATIC_FROM_FILE;
		fileNameIndex++;
	    }
	    if (args[i].equals("-ascii")) {
		plotType = ASCII_FILE;
		fileNameIndex++;
	    }
	    if (args[i].equals("-dtd")) {
		thisPlot.dtdFile = args[++i];
		fileNameIndex += 2;
	    }
	    if (args[i].equals("-rescalestep")) {
		try {
		    rescaleStep = Integer.parseInt(args[++i]);
		} catch (java.lang.NumberFormatException e) {
		    System.err.println("");
		    System.err.println("ERROR: -rescalestep argument found: " +
				       args[i] + ", should be int.");
		    return;
		}
		fileNameIndex += 2;
	    }
	    if (args[i].equals("-delay")) {
		try {
		    plotDelay = Integer.parseInt(args[++i]);
		} catch (java.lang.NumberFormatException e) {
		    System.err.println("");
		    System.err.println("ERROR: -delay argument found: " +
				       args[i] + ", should be int.");
		    return;
		}
		fileNameIndex += 2;
	    }
	    if (args[i].equals("-filter")) {
		i++;
		char fieldSel = Analyzer.UNSPEC;
		int commaInd;
		if ((commaInd = args[i].indexOf(',')) >= 0) {  //field sel?
		    thisPlot.filterFiles[filterFileIndex] = 
			args[i].substring(0,commaInd);
		    String symbFieldSel = 
			args[i].substring(commaInd+1, args[i].length());
		    if (symbFieldSel.equals(FIELD_SYMBS[Analyzer.QREC_LEN])) {
			fieldSel = Analyzer.QREC_LEN;
		    } else if (symbFieldSel.
			       equals(FIELD_SYMBS[Analyzer.QREC_PKTCOUNT])) {
			fieldSel = Analyzer.QREC_PKTCOUNT;
		    } else if (symbFieldSel.
			       equals(FIELD_SYMBS[Analyzer.QREC_PKTDROPCNT])) {
			fieldSel = Analyzer.QREC_PKTDROPCNT;
		    } else if (symbFieldSel.
			       equals(FIELD_SYMBS[Analyzer.QREC_PKTDIFF])) {
			fieldSel = Analyzer.QREC_PKTDIFF;
		    } else if (symbFieldSel.
			       equals(FIELD_SYMBS[Analyzer.QREC_PKTDROPDIFF])) {
			fieldSel = Analyzer.QREC_PKTDROPDIFF;
		    } else {
			System.err.println("Unrecognized field selector: " +
					   symbFieldSel);
			System.exit(1);
		    }
		} else {
		    thisPlot.filterFiles[filterFileIndex] = args[i];
		}
		fieldSelectors[filterFileIndex++] = fieldSel;
		fileNameIndex += 2;
	    }
	    if (args[i].equals("-datasets")) {
		try {
		    thisPlot.numDatasets = Integer.parseInt(args[++i]);
		} catch (java.lang.NumberFormatException e) {
		    System.err.println("");
		    System.err.println("ERROR: -datasets argument found: " +
				       args[i] + ", should be int.");
		    return;
		}
		fileNameIndex += 2;
	    }
	    /*	    if (args[i].equals("-line")) {
		thisPlot.barGraph = false;
		fileNameIndex++;
		} */
	    if (args[i].equals("-window")) {
		try {
		    thisPlot.windowSize = Float.parseFloat(args[++i]);
		} catch (java.lang.NumberFormatException e) {
		    System.err.println("");
		    System.err.println("ERROR: -window argument found: " +
				       args[i] + ", should be float.");
		    return;
		}
		fileNameIndex += 2;
	    }
	    if (args[i].equals("-win_heights")) {
		try {
		    thisPlot.plotWinHeights = Integer.parseInt(args[++i]);
		} catch (java.lang.NumberFormatException e) {
		    System.err.println("");
		    System.err.println("ERROR: -win_heights argument found: " +
				       args[i] + ", should be int.");
		    return;
		}
		fileNameIndex += 2;
	    }
	    if (args[i].equals("-help")) {
		printHelp();
		return;
	    }
	}
	if (args.length <= fileNameIndex) {
	    printHelp();
	    return;
	}
	if (args[fileNameIndex] == null) {
	    printHelp();
	    return;
	}

	// adjust dataset parameter?
	if (numFilters > thisPlot.numDatasets)
	    thisPlot.numDatasets = numFilters; // make sure its >= #filters


	// create data vector
	thisPlot.dataVector = new Object[thisPlot.numDatasets][];

	// create analyzer (file reader) object
	Analyzer ifa = new Analyzer(thisPlot.numDatasets,
				    args[fileNameIndex], "netflow.0");

	// if filter is invoked, configure it
	if (thisPlot.filterFiles != null) {
	    ifa.configFilters(thisPlot.filterFiles);
	    ifa.assignFieldSelectors(fieldSelectors);
	}
	
	// write file header (if file output)
	switch (plotType) {
	case PLOTML_FILE:
	    thisPlot.writePlotMLHeader(args[fileNameIndex], binSize);
	    break;
	default:;
	}

	// ask analyzer to create a time series vector from raw data
	try {
	    System.err.println("Reading data from file... (may take a while)");
	    ifa.binToArray(thisPlot.dataVector, binSize);
	    System.err.println("Finished reading. Generating plot/file...");
	}
	catch (streamException strex) {
	    System.err.println(strex.getMessage());
	    System.err.println("Data file not found!");
	    System.exit(1);
	}

	// write or plot data vector (to PlotML file, or live, or...)
	switch (plotType) {
	case PLOTML_FILE:
	    thisPlot.writePlotMLdata(binSize);
	    break;
	case LIVE_FROM_FILE:
	case STATIC_FROM_FILE:

	    // create window
	    TimeSeriesAppComponent tsc = null;
	    try {
		tsc = new TimeSeriesAppComponent(thisPlot.numDatasets, 
						 args[fileNameIndex], 
						 thisPlot.plotWinHeights 
						 * thisPlot.numDatasets);
	    } catch (Exception e) {
		return;
	    }
	    tsc.setTitle(args[fileNameIndex]);
	    tsc.setBinSize(binSize);
	    tsc.setTimeWindowSize(thisPlot.windowSize);
	    tsc.setRescaleStep(rescaleStep);

	    // set data set names (filter file name) and field type names
	    for (int i=0; i<thisPlot.numDatasets; i++) {

		// data set name
		String filterFileName = null;
		if (i < numFilters) {  // filter for this dataset?
		    filterFileName = thisPlot.filterFiles[i];
		    if (filterFileName.endsWith(".dml")) {
			// truncate off ending (it's "always" the same)
			int endIndex = filterFileName.length()-4;
			filterFileName = 
			    filterFileName.substring(0,endIndex);
		    }
		    int startIndex = filterFileName.lastIndexOf('/');
		    if (startIndex > -1) { // path prefix?
			// truncate off leading path
			filterFileName =
			    filterFileName.substring(startIndex + 1, 
						     filterFileName.length());
		    }
		    
		    tsc.getPlot(i).setDatasetName(filterFileName);
		}
	    
		// field type
		String fieldSelector = null;
		if (fieldSelectors != null) { // list of field selectors def'd?
		    fieldSelector = FIELD_SYMBS[fieldSelectors[i]];
		    tsc.getPlot(i).setFieldSelectorName(fieldSelector);
		}
	    }

	    if (plotType == LIVE_FROM_FILE) {
		while (true) {
		    tsc.addPoints(thisPlot.dataVector);
		    try {
			Thread.sleep(plotDelay);
		    } catch (InterruptedException e) {}
		}
	    } else { // STATIC_FROM_FILE
		// just call once
		tsc.addAllPoints(thisPlot.dataVector);
	    }
	    break;
	case ASCII_FILE:
	    thisPlot.writeAsciidata(binSize);
	    break;
	default:;
	}

	// write file trailer (if file output)
	switch (plotType) {
	case PLOTML_FILE:
	    thisPlot.writePlotMLTrailer();
	    break;
	default:;
	}
    }
}


/*=                                                                      =*/
/*=  Copyright (c) 1997--2000  SSF Research Network                      =*/
/*=                                                                      =*/
/*=  SSFNet is open source software, distributed under the GNU General   =*/
/*=  Public License.  See the file COPYING in the 'doc' subdirectory of  =*/
/*=  the SSFNet distribution, or http://www.fsf.org/copyleft/gpl.html    =*/
/*=                                                                      =*/
